﻿using System;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using Csla;
using Csla.Rules;
using Csla.Rules.CommonRules;
using Csla.Server;
using BizControl.DalLinq;
using Csla.Data;
using Csla.Reflection;
using System.Data.Linq;
using System.Text.RegularExpressions;
using BizControl.Library.BusinessObjects.Notes;
using BizControl.Library.Security;
using BizControl.Resources;
using Csla.Core;
using System.Collections.Generic;
using System.Security;
using BizControl.Library.Criterias;
using Microsoft.Practices.EnterpriseLibrary.Security.Cryptography;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;

namespace BizControl.Library.BusinessObjects.People
{
  [Serializable()]
  public class PersonBO : BusinessBase<PersonBO>, ISupportEmptyValue
  {
    private static string DefaultPasswordSalt = @"DefaultPasswordSalt";

    #region Business Methods

    private static readonly PropertyInfo<Guid> PersonIdProperty = RegisterProperty<Guid>(c => c.PersonId);
    [Required(AllowEmptyStrings = false, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"PersonPersonIdRequiredPropertyError")]
    public Guid PersonId
    {
      get { return GetProperty(PersonIdProperty); }
      private set { SetProperty(PersonIdProperty, value); }
    }

    private static readonly PropertyInfo<string> FirstNameProperty = RegisterProperty<string>(c => c.FirstName);
    [Required(AllowEmptyStrings = false, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"PersonFirstNameRequiredPropertyError")]
    [StringLength(50, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"PersonFirstNameLengthPropertyError")]
    [Display(ResourceType = typeof(PropertiesNames), Name = @"PersonFirstNameProperty")]
    public string FirstName
    {
      get { return GetProperty(FirstNameProperty); }
      set { SetProperty(FirstNameProperty, value); OnPropertyChanged(FullName); }
    }

    private static readonly PropertyInfo<string> LastNameProperty = RegisterProperty<string>(c => c.LastName);
    [Required(AllowEmptyStrings = false, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"PersonLastNameRequiredPropertyError")]
    [StringLength(50, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"PersonLastNameLengthPropertyError")]
    [Display(ResourceType = typeof(PropertiesNames), Name = @"PersonLastNameProperty")]
    public string LastName
    {
      get { return GetProperty(LastNameProperty); }
      set { SetProperty(LastNameProperty, value); OnPropertyChanged(FullName); }
    }

    private static readonly PropertyInfo<string> MiddleNameProperty = RegisterProperty<string>(c => c.MiddleName);
    [StringLength(50, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"PersonMiddleNameLengthPropertyError")]
    [Display(ResourceType = typeof(PropertiesNames), Name = @"PersonMiddleNameProperty")]
    public string MiddleName
    {
      get { return GetProperty(MiddleNameProperty); }
      set { SetProperty(MiddleNameProperty, value); }
    }

    private static readonly PropertyInfo<string> FullNameProperty = RegisterProperty<string>(c => c.FullName);
    [Display(ResourceType = typeof(PropertiesNames), Name = @"PersonFullNameProperty")]
    public string FullName
    {
      get { return GetProperty(FirstNameProperty) + " " + GetProperty(LastNameProperty); }
    }

    private static readonly PropertyInfo<Binary> PhotoProperty = RegisterProperty<Binary>(c => c.Photo);
    [Display(ResourceType = typeof(PropertiesNames), Name = @"PersonPhotoProperty")]
    public Binary Photo
    {
      get { return GetProperty(PhotoProperty); }
      set { SetProperty(PhotoProperty, value); }
    }

    private static readonly PropertyInfo<string> EmailProperty = RegisterProperty<string>(c => c.Email);
    [StringLength(50, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"PersonEmailLengthPropertyError")]
    [RegularExpression(@"^(([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5}){1,25})+",
      ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"PersonEmailIncorrectPropertyError")]
    [Display(ResourceType = typeof(PropertiesNames), Name = @"PersonEmailProperty")]
    public string Email
    {
      get { return GetProperty(EmailProperty); }
      set { SetProperty(EmailProperty, value); }
    }

    private static readonly PropertyInfo<PersonInfo> UserTypeProperty = RegisterProperty<PersonInfo>(c => c.UserType);
    [Display(ResourceType = typeof(PropertiesNames), Name = @"UserTypeProperty")]
    public PersonInfo UserType
    {
      get { return GetProperty(UserTypeProperty); }
      private set { SetProperty(UserTypeProperty, value); }
    }

    private static readonly PropertyInfo<string> PasswordHashProperty = RegisterProperty<string>(c => c.PasswordHash);
    [Required(AllowEmptyStrings = false)]
    [StringLength(256)]
    public string PasswordHash
    {
      get { return GetProperty(PasswordHashProperty); }
      set { SetProperty(PasswordHashProperty, value); }
    }

    private static readonly PropertyInfo<string> PasswordSaltProperty = RegisterProperty<string>(c => c.PasswordSalt);
    [Required(AllowEmptyStrings = false)]
    [StringLength(256)]
    public string PasswordSalt
    {
      get { return GetProperty(PasswordSaltProperty); }
      set { SetProperty(PasswordSaltProperty, value); }
    }

    private static readonly PropertyInfo<string> LoginProperty = RegisterProperty<string>(c => c.Login);
    [Required(AllowEmptyStrings = false, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"PersonLoginRequiredPropertyError")]
    [StringLength(40, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"PersonLoginLengthPropertyError")]
    [Display(ResourceType = typeof(PropertiesNames), Name = @"PersonLoginProperty")]
    public string Login
    {
      get { return GetProperty(LoginProperty); }
      set { SetProperty(LoginProperty, value); }
    }

    private static readonly PropertyInfo<string> CommentProperty = RegisterProperty<string>(c => c.Comment);
    [Display(ResourceType = typeof(PropertiesNames), Name = @"CommentProperty")]
    public string Comment
    {
      get { return GetProperty(CommentProperty); }
      set { SetProperty(CommentProperty, value); }
    }

    private static readonly PropertyInfo<bool> HasMarksProperty = RegisterProperty<bool>(c => c.HasMarks);
    public bool HasMarks
    {
      get { return Marks != null && Marks.Count > 0; }
      set
      {
        OnPropertyChanged(HasMarksProperty);
      }
    }

    //private static readonly PropertyInfo<bool> IsLoginProperty = RegisterProperty<bool>(c => c.IsLogin);
    //public bool IsLogin
    //{
    //  get { return GetProperty(IsLoginProperty); }
    //  set { SetProperty(IsLoginProperty, value); LoadMarks(); }
    //}

    #region Common properties

    private static readonly PropertyInfo<bool> IsDeletedFieldProperty = RegisterProperty<bool>(c => c.IsDeletedField, null, false);
    [Required(AllowEmptyStrings = false, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"IsDeletedRequiredError")]
    [Display(ResourceType = typeof(PropertiesNames), Name = @"IsDeletedFieldProperty")]
    public bool IsDeletedField
    {
      get { return GetProperty(IsDeletedFieldProperty); }
      private set { SetProperty(IsDeletedFieldProperty, value); }
    }

    private static readonly PropertyInfo<PersonInfo> CreatedByProperty = RegisterProperty<PersonInfo>(c => c.CreatedBy);
    [Required(AllowEmptyStrings = false, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"CreatedByRequiredPropertyError")]
    [Display(ResourceType = typeof(PropertiesNames), Name = @"CreatedByProperty")]
    public PersonInfo CreatedBy
    {
      get { return GetProperty(CreatedByProperty); }
      private set { SetProperty(CreatedByProperty, value); }
    }

    private static readonly PropertyInfo<DateTime> CreatedOnProperty = RegisterProperty<DateTime>(c => c.CreatedOn);
    [Required(AllowEmptyStrings = false, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"CreatedOnRequiredPropertyError")]
    [Display(ResourceType = typeof(PropertiesNames), Name = @"CreatedOnProperty")]
    public DateTime CreatedOn
    {
      get { return GetProperty(CreatedOnProperty); }
      private set { SetProperty(CreatedOnProperty, value); }
    }

    private static readonly PropertyInfo<DateTime> LastChangedProperty = RegisterProperty<DateTime>(c => c.LastChanged);
    [Required(AllowEmptyStrings = false, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"LastChangedRequiredPropertyError")]
    [Display(ResourceType = typeof(PropertiesNames), Name = @"LastChangedProperty")]
    public DateTime LastChanged
    {
      get { return GetProperty(LastChangedProperty); }
      private set { SetProperty(LastChangedProperty, value); }
    }

    private static readonly PropertyInfo<PersonInfo> LastChangedByProperty = RegisterProperty<PersonInfo>(c => c.LastChangedBy);
    [Required(AllowEmptyStrings = false, ErrorMessageResourceType = typeof(ValidationErrors), ErrorMessageResourceName = @"LastChangedByRequiredPropertyError")]
    [Display(ResourceType = typeof(PropertiesNames), Name = @"LastChangedByProperty")]
    public PersonInfo LastChangedBy
    {
      get { return GetProperty(LastChangedByProperty); }
      private set { SetProperty(LastChangedByProperty, value); }
    }

    private static readonly PropertyInfo<byte[]> TimeStampProperty = RegisterProperty<byte[]>(c => c.TimeStamp);
    private byte[] TimeStamp
    {
      get { return GetProperty(TimeStampProperty); }
      set { SetProperty(TimeStampProperty, value); }
    }

    #endregion

    #region Children

    private static readonly PropertyInfo<NoteList> NotesProperty = RegisterProperty<NoteList>(p => p.Notes, RelationshipTypes.Child | RelationshipTypes.LazyLoad);
    public NoteList Notes
    {
      get
      {
        if (!(FieldManager.FieldExists(NotesProperty)))
          Notes = NoteList.NewNotes(this);
        return GetProperty(NotesProperty);
      }
      private set
      {
        LoadProperty(NotesProperty, value);
        OnPropertyChanged(NotesProperty);
      }
    }

    private static readonly PropertyInfo<PersonModels> ModelsProperty = RegisterProperty<PersonModels>(p => p.Models, RelationshipTypes.Child | RelationshipTypes.LazyLoad);
    public PersonModels Models
    {
      get
      {
        if (!(FieldManager.FieldExists(ModelsProperty)))
          Models = PersonModels.NewPersonModels(this);
        return GetProperty(ModelsProperty);
      }
      private set
      {
        LoadProperty(ModelsProperty, value);
        OnPropertyChanged(ModelsProperty);
      }
    }

    private static readonly PropertyInfo<PersonMarks> MarksProperty = RegisterProperty<PersonMarks>(p => p.Marks, RelationshipTypes.Child | RelationshipTypes.LazyLoad);
    public PersonMarks Marks
    {
      get
      {
        if (!(FieldManager.FieldExists(MarksProperty)))
          Marks = PersonMarks.NewPersonMarks(this);
        return GetProperty(MarksProperty);
      }
      private set
      {
        LoadProperty(MarksProperty, value);
        OnPropertyChanged(MarksProperty);
      }
    }

    #endregion

    internal void LoadData(Person personDB)
    {
      if (personDB == null) return;
      PersonId = personDB.PersonId;
      FirstName = personDB.FirstName;
      LastName = personDB.LastName;
      MiddleName = personDB.MiddleName;
      Photo = personDB.Photo;
      Email = personDB.Email;
      UserType = new PersonInfo(personDB.UserTypeRef);
      Login = personDB.Login;
      PasswordHash = personDB.PasswordHash;
      PasswordSalt = personDB.PasswordSalt;
      Comment = personDB.Comment;

      // Common properties
      CreatedBy = new PersonInfo(personDB.CreatedByRef);
      LastChangedBy = new PersonInfo(personDB.LastChangedByRef);

      CreatedOn = personDB.CreatedOn;
      LastChanged = personDB.LastChanged;

      IsDeletedField = personDB.IsDeleted;
      TimeStamp = personDB.TimeStamp.ToArray();
    }

    public override string ToString()
    {
      return PersonId.ToString();
    }

    #endregion

    #region Business Rules

    private List<string> _roles = new List<string>();
    protected override void AddBusinessRules()
    {
      base.AddBusinessRules();
      BusinessRules.AddRule(new DynamicIsInRole(AuthorizationActions.ReadProperty, NotesProperty));
      BusinessRules.AddRule(new DynamicIsInRole(AuthorizationActions.WriteProperty, NotesProperty));

      //BusinessRules.AddRule(new EmailIsCorrect{ PrimaryProperty = EmailProperty });
    }

    private class DynamicIsInRole : AuthorizationRule
    {
      private AuthorizationActions _action;
      private IMemberInfo _element;

      public DynamicIsInRole(AuthorizationActions action, IMemberInfo element)
        : base(action, element)
      {
        _action = action;
        _element = element;
      }

      protected override void Execute(AuthorizationContext context)
      {
        var target = (PersonBO)context.Target;
        if (target.PersonId != Guid.Empty)
        {
          target._roles.Clear();
          target._roles.Add(target.PersonId.ToString());
        }
        if (target._roles.Count > 0)
        {
          foreach (var item in target._roles)
            if (Csla.ApplicationContext.User.IsInRole(item))
            {
              context.HasPermission = true;
              break;
            }
        }
        else
        {
          context.HasPermission = true;
        }
      }
    }

    private class EmailIsCorrect : BusinessRule
    {
      protected override void Execute(RuleContext context)
      {
        var target = (PersonBO)context.Target;
        string email = target.ReadProperty(EmailProperty);
        string pattern = @"^(([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5}){1,25})+";
        if (!string.IsNullOrEmpty(email) && !Regex.IsMatch(email, pattern))
          context.AddErrorResult(Resources.ValidationErrors.PersonEmailIncorrectPropertyError);
      }
    }

    #endregion

    #region Authorization rules

    private static void AddObjectAuthorizationRules()
    {
      //BusinessRules.AddRule(typeof(PersonBO), new IsInRole(AuthorizationActions.CreateObject, "PersonBO.Get()"));
      //BusinessRules.AddRule(typeof(PersonBO), new IsInRole(AuthorizationActions.GetObject, "PersonBO.Get()"));
      //BusinessRules.AddRule(typeof(PersonBO), new IsInRole(AuthorizationActions.EditObject, "PersonBO.Edit()"));
      //BusinessRules.AddRule(typeof(PersonBO), new IsInRole(AuthorizationActions.DeleteObject, "PersonBO.Delete()"));
    }

    #endregion

    #region Data Access

    //[RunLocal]
    protected override void DataPortal_Create()
    {
      var currentUser = SecurityChecks.GetCurrentUser();
      using (BypassPropertyChecks)
      {
        PersonId = Guid.NewGuid();
        CreatedBy = new PersonInfo(currentUser);
        LastChangedBy = new PersonInfo(currentUser);
        CreatedOn = DateTime.Now;

        PasswordSalt = DefaultPasswordSalt;
      }
      base.DataPortal_Create();
    }

    private void DataPortal_Fetch(IdAndBoolCriteria criteria)
    {
      using (var ctx = ContextManager<BusinessControl2DataContext>.GetManager(Database.BizControlDB))
      {
        var data = (from p in ctx.DataContext.People
                    where p.PersonId == criteria.Id && !p.IsDeleted
                    select p).SingleOrDefault();
        using (BypassPropertyChecks)
        {
          this.LoadData(data);
        }
        // get child data
        if (data != null)
        {
          Notes = NoteList.GetNotes(data.NotesCreatedBy.ToArray(), this);
          Models = PersonModels.GetModelPeople(data.PersonModels.ToArray(), this);
          //IsLogin = criteria.Bool;
          if (!criteria.Bool)
          {
            var currentModel = SecurityChecks.GetCurrentModel();
            var filteredData = (from p in data.PersonMarks
                                from m in ctx.DataContext.Marks
                                where p.Mark == m.MarkId && !p.IsDeleted && !m.IsDeleted && m.Model == currentModel.ModelId
                                select p);
            Marks = PersonMarks.GetPersonMarks(filteredData.ToArray(), this);
          }
        }
      }
    }

    private void LoadMarks(Person data)
    {
      //if (!IsLogin)
      //{
      //  using (var ctx = ContextManager<BusinessControl2DataContext>.GetManager(Database.BizControlDB))
      //  {
      //    var data = (from p in ctx.DataContext.People
      //                where p.PersonId == PersonId && !p.IsDeleted
      //                select p).SingleOrDefault();
          //if (data != null)
          //{
          //  var currentModel = SecurityChecks.GetCurrentModel();
          //  var filteredData = (from p in data.PersonMarks
          //                      from m in ctx.DataContext.Marks
          //                      where p.Mark == m.MarkId && !p.IsDeleted && !m.IsDeleted && m.Model == currentModel.ModelId
          //                      select p);
          //  Marks = PersonMarks.GetPersonMarks(filteredData.ToArray(), this);
          //}
      //  }
      //}
    }

    [Transactional(TransactionalTypes.TransactionScope)]
    protected override void DataPortal_Insert()
    {
      using (var ctx = ContextManager<BusinessControl2DataContext>.GetManager(Database.BizControlDB))
      {
        Binary lastTimeStamp = null;
        LastChanged = DateTime.Now;
        CryptographyManager defaultCrypto = EnterpriseLibraryContainer.Current.GetInstance<CryptographyManager>();
        PasswordHash = defaultCrypto.CreateHash(BizControl.Library.Constants.ConfiguraionNames.CryptoProvider512, PasswordHash);
        using (BypassPropertyChecks)
        {
          ctx.DataContext.AddPerson(
            PersonId,
            LastName,
            FirstName,
            MiddleName,
            Photo,
            Email,
            UserType == null || UserType.IsEmpty() ? (Guid?)null : UserType.Id,
            PasswordHash,
            PasswordSalt,
            Login,
            Comment,
            CreatedBy == null || CreatedBy.IsEmpty() ? (Guid?)null : CreatedBy.Id,
            IsDeletedField,
            CreatedOn,
            LastChanged,
            LastChangedBy == null || LastChangedBy.IsEmpty() ? (Guid?)null : LastChangedBy.Id,
            ref lastTimeStamp);
          TimeStamp = lastTimeStamp.ToArray();
        }
        // update child objects
        Csla.DataPortal.UpdateChild(Notes, this);
        Csla.DataPortal.UpdateChild(Marks, this);
        Csla.DataPortal.UpdateChild(Models, this);
        //FieldManager.UpdateChildren(this);
      }
      AddPersonToModel();
    }

    void AddPersonToModel()
    {
      var model = SecurityChecks.GetCurrentModel();
      model.People.AddPerson(this);
      ((BizControlPrincipal)ApplicationContext.User).Model = model.Save();
    }
    
    [Transactional(TransactionalTypes.TransactionScope)]
    protected override void DataPortal_Update()
    {
      using (var ctx = ContextManager<BusinessControl2DataContext>.GetManager(Database.BizControlDB))
      {
        if (IsSelfDirty)
        {
          Binary lastTimeStamp = null;
          LastChangedBy = new PersonInfo(SecurityChecks.GetCurrentUser());
          LastChanged = DateTime.Now;
          using (BypassPropertyChecks)
          {
            ctx.DataContext.UpdatePerson(
              PersonId,
              LastName,
              FirstName,
              MiddleName,
              Photo,
              Email,
              UserType == null || UserType.IsEmpty() ? (Guid?)null : UserType.Id,
              PasswordHash,
              PasswordSalt,
              Login,
              Comment,
              IsDeletedField,
              LastChanged,
              LastChangedBy == null || LastChangedBy.IsEmpty() ? (Guid?)null : LastChangedBy.Id,
              TimeStamp,
              ref lastTimeStamp);
            TimeStamp = lastTimeStamp.ToArray();
          }
        }
        // update child objects
        FieldManager.UpdateChildren(this);
      }
    }

    [Transactional(TransactionalTypes.TransactionScope)]
    protected override void DataPortal_DeleteSelf()
    {
      DataPortal_Delete(new SingleCriteria<PersonBO, Guid>(PersonId));
    }

    [Transactional(TransactionalTypes.TransactionScope)]
    private void DataPortal_Delete(SingleCriteria<PersonBO, Guid> criteria)
    {
      IsDeletedField = true;
      DataPortal_Update();
    }

    #endregion

    #region Factory Methods

    private PersonBO()
    { /* require use of factory methods */ }

    public static PersonBO NewPerson()
    {
      return Csla.DataPortal.Create<PersonBO>();
    }

    public static PersonBO GetPerson(Guid id, bool isLogin = false)
    {
      return Csla.DataPortal.Fetch<PersonBO>(new IdAndBoolCriteria(id, isLogin));
    }

    private static PersonBO _emptyPerson;
    public static PersonBO GetEmptyPerson()
    {
      if (_emptyPerson == null)
      {
        _emptyPerson = new PersonBO();
        _emptyPerson.PersonId = Guid.Empty;
        _emptyPerson.FirstName = PropertiesNames.EmptyName;
        _emptyPerson.LastName = PropertiesNames.EmptyName;
      }
      return _emptyPerson;
    }

    #endregion

    #region ISupportEmtpyValue Members

    public bool IsEmpty()
    {
      return PersonId == Guid.Empty;
    }

    #endregion
  }
}